﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace SearchScreen.SDK
{
    public class FrameworkElementFactoryFactory : IPartImportsSatisfiedNotification
    {
        public interface ImportMetaData
        {
            string Field { get; }
        }

        private static FrameworkElementFactoryFactory instance;

        public static FrameworkElementFactoryFactory Instance
        {
            get
            {
                if (null == instance)
                {
                    lock (typeof(FrameworkElementFactoryFactory))
                    {
                        if (null == instance)
                        {
                            instance = new FrameworkElementFactoryFactory();

                            var container = new CompositionContainer(
                            new AggregateCatalog(
                              new AssemblyCatalog(typeof(FrameworkElementFactoryFactory).Assembly),
                              new DirectoryCatalog(@"C:\Projects\SearchScreen\SearchScreen.ExtendedComponentSample\bin\Debug")));

                            container.SatisfyImportsOnce(instance);
                        }
                    }
                }

                return instance;
            }
        }


        [ImportMany]
        private List<Lazy<IFrameworkElementFactoryProvider, ImportMetaData>> knownFactories = null;

        private IDictionary<string, Lazy<IFrameworkElementFactoryProvider>> FactoryCache { get; set; }

        public void OnImportsSatisfied()
        {
            this.FactoryCache = this.knownFactories.ToDictionary(
                kp => kp.Metadata.Field,
                kp => new Lazy<IFrameworkElementFactoryProvider>(() => kp.Value));
        }

        public FrameworkElementFactory GetFactory(SearchCriteriaAttribute searchCriteria, PropertyInfo targetProperty)
        {
            Lazy<IFrameworkElementFactoryProvider> lazyFactory = null;

            if (!FactoryCache.TryGetValue(searchCriteria.Id, out lazyFactory))
            {
                switch (searchCriteria.ConsideredType)
                {
                    case "DatePicker":
                        lazyFactory = new Lazy<IFrameworkElementFactoryProvider>(
                            () => new DelegateFrameworkElementFactory((p) =>
                            {
                                var elementFactory = new FrameworkElementFactory();
                                elementFactory.Type = typeof(DatePicker);
                                elementFactory.SetBinding(DatePicker.SelectedDateProperty, p);
                                return elementFactory;
                            }));
                        break;

                    case "CheckBox":
                        lazyFactory = new Lazy<IFrameworkElementFactoryProvider>(
                            () => new DelegateFrameworkElementFactory((p) =>
                            {
                                var elementFactory = new FrameworkElementFactory();
                                elementFactory.Type = typeof(CheckBox);
                                elementFactory.SetBinding(CheckBox.IsCheckedProperty, p);
                                return elementFactory;
                            }));
                        break;

                    case "TextBox":
                    default:
                        lazyFactory = new Lazy<IFrameworkElementFactoryProvider>(
                            () => new DelegateFrameworkElementFactory((p) =>
                            {
                                var elementFactory = new FrameworkElementFactory();
                                elementFactory.Type = typeof(TextBox);
                                elementFactory.SetBinding(TextBox.TextProperty, p);
                                return elementFactory;
                            }));
                        break;

                }
                // add the newly created factroy to the cache
                this.FactoryCache.Add(searchCriteria.Id, lazyFactory);
            }

            if (null == lazyFactory)
            {
                throw new InvalidOperationException(
                    string.Format("No Factory found for field ID {0}",
                    searchCriteria.Id));
            }

            var targetPropertyBinding = new Binding
            {
                Path = new PropertyPath(targetProperty.Name),
                Mode = BindingMode.TwoWay
            };

            return lazyFactory.Value.GetFactory(targetPropertyBinding);
        }

    }

    internal class DelegateFrameworkElementFactory : IFrameworkElementFactoryProvider
    {
        Func<Binding, FrameworkElementFactory> func;

        public DelegateFrameworkElementFactory(Func<Binding, FrameworkElementFactory> func)
        {
            this.func = func;
        }

        public FrameworkElementFactory GetFactory(Binding targetPropertyBinding)
        {
            return func(targetPropertyBinding);
        }
    }
}
